package com.espirit.eap.sa;

import java.math.BigInteger;
import java.security.MessageDigest;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collection;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import javax.annotation.PostConstruct;
import javax.annotation.Resource;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import javax.servlet.http.HttpServletRequest;

import org.apache.commons.codec.binary.Base64;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang.time.DateUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.struts2.ServletActionContext;
import org.apache.struts2.StrutsStatics;
import org.apache.struts2.dispatcher.ServletRedirectResult;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import com.espirit.common.MailService;
import com.espirit.eap.manager.I18n;
import com.espirit.eap.manager.I18nService;
import com.espirit.eap.manager.SystemData;
import com.espirit.eap.manager.SystemDataService;
import com.espirit.eap.pagelayout.Model;
import com.espirit.eap.pagelayout.Page;
import com.espirit.eap.sa.PrivilegeService.GroupPrivilege;
import com.googlecode.cswish.Config;
import com.googlecode.cswish.annotation.ParamScope;
import com.googlecode.cswish.annotation.Protected;
import com.googlecode.cswish.annotation.Rest;
import com.googlecode.cswish.annotation.ScopeValue;
import com.googlecode.cswish.exception.ApplicationException;
import com.googlecode.cswish.model.PageElement;
import com.googlecode.cswish.model.PageElements;
import com.googlecode.cswish.model.PageInfo;
import com.googlecode.cswish.model.ParameterMap;
import com.googlecode.cswish.model.Reference;
import com.googlecode.cswish.oosearch.QLInfo;
import com.googlecode.cswish.security.Context;
import com.googlecode.cswish.security.LoginCallback;
import com.googlecode.cswish.security.SafeManager;
import com.googlecode.cswish.security.auth.UserInfo;
import com.googlecode.cswish.security.type.FreemarkerSQLRule;
import com.googlecode.cswish.security.type.PermissionRule;
import com.googlecode.cswish.security.type.RecordRuleSetOr;
import com.googlecode.cswish.struts.ContextHolder;
import com.googlecode.cswish.struts.Initialization;
import com.googlecode.cswish.struts.Invoker;
import com.googlecode.cswish.struts.conversion.PageElementService;
import com.googlecode.cswish.struts.interceptor.ExceptionMappingInterceptor;
import com.googlecode.cswish.struts.spring.BeanIntrospector;
import com.googlecode.cswish.struts.spring.CacheService;
import com.googlecode.cswish.struts.spring.Executor;
import com.googlecode.cswish.struts.spring.GenericService;
import com.googlecode.cswish.struts.spring.ObjectFactory;
import com.googlecode.cswish.struts.spring.SystemEnv;
import com.googlecode.cswish.util.FrameConstant;
import com.googlecode.cswish.util.Functions;
import com.googlecode.cswish.util.SimpleEncryptUtil;
import com.opensymphony.xwork2.ActionContext;
import com.opensymphony.xwork2.Result;
import com.opensymphony.xwork2.util.ValueStack;

@Service
public class LoginUserService implements Initialization {

	private final static Log logger = LogFactory.getLog(LoginUserService.class);

	@Resource
	private GenericService genericService;

	@Resource
	private BeanIntrospector beanIntrospector;

	@Resource
	private SafeManager safeManager;

	@Resource
	private ObjectFactory objectFactory;

	@Resource
	private Functions functions;

	@Resource
	private PrivilegeService privilegeService;

	@Resource
	private MailService mailService;

	@Resource
	private Config config;

	@Resource
	private CacheService cacheService;

	@PersistenceContext(unitName = "espirit")
	protected EntityManager em;

	@Resource
	private I18nService i18nService;


	@Resource
	private PageElementService pageElementService;

	@Resource
	private SystemDataService systemDataService;

	@Resource
	private WebsiteService websiteService;

	private LoginUser adminUser;

	protected List<String> commonPermitPath;

	public int maxEnabledLoginCount;

	private int loginCount;

	private LoginUserService userService;

	private Executor<String, PathFieldPermission> fieldPermissionParser;

	public LoginUserService getUserService() {
		if (userService == null) {
			userService = objectFactory.getBean(LoginUserService.class, false);
		}
		return userService;
	}

	public LoginUserService() {
		commonPermitPath = new ArrayList<String>();

		// mock a free path for all the @Rest(simpleServiceName="loginUser", type="free")
		commonPermitPath.add("/loginUser.free.html");
		commonPermitPath.add("/espirit/eap/manager/i18n.common.html");
	}

	public class LoginUserCallback implements LoginCallback {
		@Override
		public UserInfo execute(SafeManager safeManager, String userId, String site) {
			ContextHolder context = ContextHolder.get();
			String oldSite = context.getSiteName();
			try {
				context.setSiteName(site);
				UserInfo userInfo = getUserService().loginUserBySite(userId, site, null);
				return userInfo;
			} finally {
				// restore site
				context.setSiteName(oldSite);
			}
		}
	}

	@PostConstruct
	private void init() {
		fieldPermissionParser = new FieldPermissionParser();
		loginUserCache = cacheService.newCache(String.class);
		safeManager.setDefaultLoginCallback(new LoginUserCallback());

		safeManager.setDefaultSite(websiteService.getDefaultWebsiteName());
		functions.registCommonService("loginUser", this);
	}

	@Override
	public String[] getProductName() {
		return Website.DEFAULT_NAMES;
	}
	
	public String getCurrentProductName() {
		return ContextHolder.get().getProductName();
	}

	@Override
	public void initSite() {
		Page example = new Page();
		example.setFreeAccess(Boolean.TRUE);
		List<Page> pages = genericService.searchAll(example).getData();

		for (Page page : pages) {
			// functions.linkNoBase(actionPath, true)
			safeManager.addFreePath(functions.link(page.getPath()));
		}
		// common free path
		safeManager.addFreePath(functions.link("/anonymity.free.html"));
	}

	public void _add(
			LoginUser model,
			@ParamScope(scopeKey = "'Privilege.applySelectedPrivileges'", visible = false, scope = ScopeValue.SESSION) Reference<UserTemplate> selectedPrivileges) {
		selectedPrivileges.set(null);
	}

	@Transactional
	public LoginUser add(
			LoginUser model,
			List<String> privileges,
			@ParamScope(scopeKey = "'Privilege.applySelectedPrivileges'", visible = false, scope = ScopeValue.SESSION) Reference<UserTemplate> selectedPrivileges) {
		return update(model, privileges, selectedPrivileges);
	}

	public boolean deleteList(List<LoginUser> results) {
		boolean ret = true;
		if (results != null) {
			for (LoginUser theModel : results) {
				if (theModel != null) {
					ret &= delete(theModel);
				}
			}
		}
		return ret;
	}

	public boolean delete(LoginUser model) {
		LoginUser dbModel = genericService.load(model);
		if (dbModel.isSuperAdmin()) {
			return false;
		}

		return genericService.delete(dbModel, false);
	}

	@Rest(type = "update")
	public Object _updateFieldPermission(LoginUser model) {
		return privilegeService.getGroupPrivilege(model.getId());
	}

	@Rest(type = "update")
	public void updateFieldPermission(String category, String model, String method, Integer userId,
			List<FieldPermission> fieldPermissions, Boolean modelPrivilege) {
		List<FieldPermission> validatedFieldPermissions = new ArrayList<FieldPermission>();
		for (FieldPermission fieldPermission : fieldPermissions) {
			// same with the default value, don't save
			boolean scopeTypeChanged = fieldPermission.getScopeType() != null
					&& !fieldPermission.getScopeType().equals(ScopeType.Global)
					|| fieldPermission.getWriteScopeType() != null
					&& !fieldPermission.getWriteScopeType().equals(ScopeType.Global);
			// scope type changed or default permission type changed
			if (scopeTypeChanged || fieldPermission.getFieldPermissionType() != null
					&& !fieldPermission.getEnableFieldPermissionType().equals(fieldPermission.getFieldPermissionType())) {
				validatedFieldPermissions.add(fieldPermission);
			}
		}

		if (modelPrivilege != null && modelPrivilege) {
			updateModelFieldPermission(model, method, userId, validatedFieldPermissions);
		} else {
			updatePageFieldPermission(model, method, userId, validatedFieldPermissions);
		}
		// TODO: update the permission later?
		safeManager.updatePermission(ServletActionContext.getRequest(), userId.toString(), null); // update
																									// the
																									// default
																									// site
																									// permission
		if (method != null) {
			pageElementService.reset(model, method);

			// mapping model
			String ql = "from " + Privilege.class.getName() + " a where a.mappingModelSimpleName=?";
			QLInfo qlInfo = new QLInfo(ql, true, model);
			Privilege thePrivilege = (Privilege) genericService.searchByQl(qlInfo, false).getData(0);
			if (thePrivilege != null && thePrivilege.getModelSimpleName() != null) {
				pageElementService.reset(thePrivilege.getModelSimpleName(), method);
			}
		}
	}

	private void updateModelFieldPermission(String model, String method, Integer userId,
			List<FieldPermission> validatedFieldPermissions) {
		// FIXME: temporary solution, try to convert the mapping model to real
		// model
		Privilege privilegeExample = new Privilege();
		privilegeExample.setMappingModelSimpleName(model);
		Privilege mapping = genericService.searchAll(privilegeExample).getData(0);
		if (mapping != null) {
			model = mapping.getModelSimpleName();
		}

		// search all
		UserTemplate userTemplate = genericService.load(UserTemplate.class, userId);
		List<ModelFieldPermission> fieldPermissions = userTemplate.getModelFieldPermissions();

		Map<String, ModelFieldPermission> existedPermissions = new HashMap<String, ModelFieldPermission>();
		if (fieldPermissions != null) {
			for (ModelFieldPermission fieldPermission : fieldPermissions) {
				existedPermissions.put(fieldPermission.getModelSimpleName() + '#' + fieldPermission.getFieldName(),
						fieldPermission);
			}
		}

		// add new ModelFieldPermission
		if (validatedFieldPermissions != null && validatedFieldPermissions.size() > 0) {
			for (FieldPermission fieldPermission : validatedFieldPermissions) {
				// mapping the common field permission to different method
				FieldPermission existedPermission = existedPermissions.remove(model + '#'
						+ fieldPermission.getFieldName());
				FieldPermission cloneFieldPermission = fieldPermission.copyTo(existedPermission);

				if (cloneFieldPermission != existedPermission) {
					ModelFieldPermission mfp = new ModelFieldPermission();
					cloneFieldPermission.copyTo(mfp);
					mfp.setUserTemplate(userTemplate);
					mfp.setModelSimpleName(model);
					userTemplate.safeModelFieldPermissions().add(mfp);
				}
			}
		}

		// delete the remnant ModelFieldPermission(whose modelSimpleName equals
		// parameter "model")
		Collection<ModelFieldPermission> remnantPermission = existedPermissions.values();
		for (ModelFieldPermission fieldPermission : remnantPermission) {
			if (model.equals(fieldPermission.getModelSimpleName())) {
				userTemplate.safeModelFieldPermissions().remove(fieldPermission);
			}
		}

		// commit the update
		genericService.update(userTemplate);
	}

	private void updatePageFieldPermission(String model, String method, Integer userId,
			List<FieldPermission> validatedFieldPermissions) {
		// see PrivilegeService.showFieldPrivilege
		List<Object> params = new ArrayList<Object>(6);
		String ql = "from " + UserTemplatePrivilege.class.getName() + " a where a.userTemplate.id=?"
				+ " and (a.privilege.modelSimpleName=? or a.privilege.mappingModelSimpleName=?)";
		params.add(userId);
		params.add(model);
		params.add(model);

		if (Invoker.isInputMethod(method)) {
			ql += " and a.privilege.method=?";
			params.add(method);
		} else {
			ql += " and (a.privilege.method=? or a.privilege.method=?) order by a.privilege.method desc";
			String inputMethod = FrameConstant.ACTION_PREPARE + method;
			params.add(method);
			params.add(inputMethod);
		}
		QLInfo qlInfo = new QLInfo(ql, true, params);
		PageInfo pageInfo = genericService.searchByQl(qlInfo, false);
		UserTemplatePrivilege userTplPrivilege = (UserTemplatePrivilege) pageInfo.getData(0);

		if (userTplPrivilege == null) {
			Model theModel = new Model();
			theModel.setName(model);
			I18n btnI18n = i18nService.getI18n("button." + method, false);
			String modelMethodName = theModel.getI18n() + ":"
					+ (btnI18n == null ? method : btnI18n.getName());
			throw new ApplicationException("请先配置\"{0}\"的页面权限", modelMethodName);
		} else {
			List<PageFieldPermission> pageFieldPermissions = userTplPrivilege.getPageFieldPermissions();
			if (pageFieldPermissions == null) {
				pageFieldPermissions = new ArrayList<PageFieldPermission>();
			} else {
				pageFieldPermissions.clear();
			}

			// add new ModelFieldPermission
			for (FieldPermission fieldPermission : validatedFieldPermissions) {
				PageFieldPermission pageFieldPermission = new PageFieldPermission();
				fieldPermission.copyTo(pageFieldPermission);
				pageFieldPermission.setUserTemplatePrivilege(userTplPrivilege);
				pageFieldPermissions.add(pageFieldPermission);
			}
			genericService.merge(userTplPrivilege);
		}
	}

	public Object _update(
			LoginUser model,
			@ParamScope(visible = false, value = "#ps") PageElements ps,
			@ParamScope(scopeKey = "'Privilege.applySelectedPrivileges'", visible = false, scope = ScopeValue.SESSION) Reference<UserTemplate> selectedPrivileges) {
		selectedPrivileges.set(null);
		genericService.view(model);
		model.setPassword(null);
		return model;
	}

	@Transactional
	public LoginUser update(
			LoginUser model,
			List<String> privileges,
			@ParamScope(scopeKey = "'Privilege.applySelectedPrivileges'", visible = false, scope = ScopeValue.SESSION) Reference<UserTemplate> selectedPrivileges) {
		
		UserTemplate userTemplate = null;
		if (selectedPrivileges != null){
			userTemplate = selectedPrivileges.get();
		}
		if (userTemplate != null) {
			selectedPrivileges.set(null);

			// save the draft version by selected template
			userTemplate.setId(model.getId());
			UserTemplate draftVersion = genericService.merge(userTemplate);
			model.setId(draftVersion.getId());
		}

		LoginUser loginUser = getUserService().updateUserPrivilege(model, privileges);

		if (model.getId() != null) {
			// update the default site permission (current site)
			safeManager.updatePermission(ServletActionContext.getRequest(), model.getId().toString(), ContextHolder
					.get().getSiteName());
		}
		return loginUser;
	}

	@Protected
	@Transactional
	public LoginUser updateUserPrivilege(LoginUser model, List<String> privileges) {
		if (model.getPassword() != null) {
			model.setPassword(getEncryptPassword(model.getPassword()));
		} else {
			if (model.getId() != null) {
				String password = genericService.load(model).getPassword();
				model.setPassword(password);
			}
		}

		return privilegeService.update(model, privileges);
	}

	public LoginUser getLoginUser() {
		ActionContext context = ActionContext.getContext();
		if (context == null) {
			return null;
		}

		HttpServletRequest request = (HttpServletRequest) context.get(StrutsStatics.HTTP_REQUEST);
		LoginUser user = null;
		if (request != null) {
			UserInfo userInfo = safeManager.getLoginUser(request);
			if (userInfo instanceof LoginUser) {
				user = (com.espirit.eap.sa.LoginUser) userInfo;
			} else if (!safeManager.isEnablePermissionCheck()
					&& !"true".equals(System.getProperty(SystemEnv.JUNIT_TEST))) {
				user = getDefaultAdminUser();
				safeManager.login(request, String.valueOf(user.getId()), ContextHolder.get().getSiteName());
			}
		}
		return user;
	}

	public Screen getUserScreen() {
		Screen screen = (Screen) ServletActionContext.getRequest().getSession().getAttribute(FrameConstant.USER_SCREEN);
		return screen;
	}

	public void setUserScreen(Screen screen) {
		ServletActionContext.getRequest().getSession().setAttribute(FrameConstant.USER_SCREEN, screen);
	}

	public boolean hasPermission(String actionName, String methodName) {
		String simpleClassName = beanIntrospector.getSimpleName(actionName);
		String page = functions.linkModelNoBase(simpleClassName, methodName, true);
		LoginUser loginUser = getLoginUser();
		boolean hasPermission;
		if (loginUser != null) {
			String userId = String.valueOf(loginUser.getId());
			hasPermission = safeManager.checkPage(userId, page);
		} else {
			hasPermission = false;
		}
		return hasPermission;
	}

	public boolean hasLinkPermission(String link) {
		return hasLinkPermission(link, true);
	}

	public boolean hasLinkPermission(String link, boolean containBase) {
		if (link != null && (link.startsWith("http://") || link.startsWith("https://"))) {
			return true;
		}

		LoginUser loginUser = getLoginUser();
		boolean pass;
		if (loginUser != null) {
			String userId = String.valueOf(loginUser.getId());
			String page = getCheckPage(link, containBase);
			pass = safeManager.checkPage(userId, page);
		} else {
			pass = false;
		}
		return pass;
	}

	private String getCheckPage(String link, boolean containBase) {
		String page;
		if (containBase) {
			page = link.substring(Functions.getBase().length());
		} else {
			page = link;
		}
		int index = page.lastIndexOf('?');
		if (index > 0) {
			page = page.substring(0, index);
		}
		return page;
	}

	public List<Object> getRecordPermission(String link, boolean containBase, List<Object> data) {
		if (data == null || data.isEmpty()) {
			return data;
		}
		ActionContext actionContext = ActionContext.getContext();
		if (actionContext == null) {
			return data;
		}

		List<Object> permittedData;
		HttpServletRequest request = (HttpServletRequest) actionContext.get(StrutsStatics.HTTP_REQUEST);
		UserInfo loginUser = safeManager.getLoginUser(request);
		if (loginUser != null) {
			String userId = loginUser.getLoginId();
			String page = getCheckPage(link, containBase);
			Context context = new Context(actionContext.getValueStack(), functions);
			context.setUserInfo(loginUser);
			RecordRuleSetOr recordRuleSetOr = safeManager.getRecordLimitPermission(userId, page, context);
			if (recordRuleSetOr == null || RecordRuleSetOr.FALSE.equals(recordRuleSetOr)) {
				permittedData = data;
			} else {

				permittedData = new ArrayList<Object>(data.size());
				for (Object item : data) {
					context.setModel(item);
					boolean ret = recordRuleSetOr.executeBoolean(context);
					if (ret) {
						permittedData.add(item);
					}
				}
			}
		} else {
			permittedData = data;
		}
		return permittedData;
	}

	public List<String> filterLinkPermission(List<String> links, boolean keepEmptyLink) {
		List<String> permittedLinks = new ArrayList<String>();
		for (String link : links) {
			if (link.isEmpty()) {
				if (keepEmptyLink) {
					permittedLinks.add(link);
				}
			} else if (hasLinkPermission(link, false)) {
				permittedLinks.add(link);
			} else {
				if (keepEmptyLink) {
					permittedLinks.add("");
				}
			}
		}
		return permittedLinks;
	}

	public boolean hasLinkPermission(String link, String method) {
		if (method != null && method.length() > 0) {
			// Invoker invoker = routing.path2Invoker(link);
			// invoker.methodName = method;
			// link = routing.invoker2Path(invoker);
			// see Functions.replaceAction
			int end = link.indexOf('.' + functions.getActionExtension());
			if (end > 0) {
				int begin = link.lastIndexOf('.', end - 1);
				if (begin < 0) {
					begin = end;
				}
				link = link.substring(0, begin) + '.' + method + link.substring(end);
			}
		}
		boolean hasPermission = hasLinkPermission(link);
		return hasPermission;
	}

	@Rest(simpleServiceName = "anonymity", type = "free")
	public void _login(String productLogin, String productLoginDesc) {
		if (StringUtils.isEmpty(productLogin) || StringUtils.isEmpty(productLoginDesc)) {
			String site = getCurrentWebsiteName();
			ValueStack valueStack = ActionContext.getContext().getValueStack();

			I18n login = i18nService.getI18n("site.login" , false);
			String loginName = login == null ? "人力资源管理系统" : login.getName();
			
			I18n loginDesc = i18nService.getI18n("site.login.desc" , false);
			String loginDescName = loginDesc == null ? "Anywhere Anytime" : loginDesc.getName();
			
			valueStack.setValue("site", site);
			valueStack.setValue("productLogin", loginName);
			valueStack.setValue("productLoginDesc", loginDescName);
		}
	}

	@Rest(simpleServiceName = "anonymity", type = "free")
	public String login(String name, String password, boolean isEncrypted, String site) {
		HttpServletRequest request = ServletActionContext.getRequest();
		String ip = getRemoteIp(request);
		if (logger.isInfoEnabled()) {
			logger.info(name + " login from ip:" + ip);
		}

		// check attacher
		// check 1: one ip and one user only has 5 chances to login fail
		UserSecurity userSecurityExample = new UserSecurity();
		userSecurityExample.setIdentity(name + ',' + ip);
		Date today = DateUtils.truncate(Calendar.getInstance().getTime(), Calendar.DAY_OF_MONTH);
		userSecurityExample.setLoginTime(today);
		UserSecurity userSecurity = genericService.searchAll(userSecurityExample).getData(0);
		if (userSecurity != null && userSecurity.getErrCount() > 20) {
			throw new ApplicationException("账号登陆频繁，临时锁定");
		}

		// TODO: check 2: one ip only can login 100 users
		userSecurityExample.setIdentity(ip);
		userSecurity = genericService.searchAll(userSecurityExample).getData(0);
		if (userSecurity != null && userSecurity.getErrCount() > 100) {
			throw new ApplicationException("同一IP切换用户过多，临时锁定");
		}

		// login
		LoginUser dbUser;
		if (safeManager == null) { // Strange, it's null when it's called by Functions.service
			ObjectFactory.getInstance().autoWireBean(this);
		}
		boolean check = safeManager.isEnablePermissionCheck();
		if (check) {
			String encryptPassword;
			if (isEncrypted) {
				encryptPassword = password;
			} else {
				encryptPassword = getEncryptPassword(password);
			}

			QLInfo qlInfo;
			if (encryptPassword != null) {
				String ql = "from " + LoginUser.class.getName() + " a where a.name=? and a.password=?";
				qlInfo = new QLInfo(ql, true, name, encryptPassword);
			} else {
				String ql = "from " + LoginUser.class.getName() + " a where a.name=? and a.password is null";
				qlInfo = new QLInfo(ql, true, name);
			}
			PageInfo<LoginUser> pageInfo = genericService.searchByQl(qlInfo, false);
			boolean pass = pageInfo.getData().size() == 1;
			if (pass) {
				// continue to check the user status
				if (LoginUser.STATUS_INVALID.equals(pageInfo.getData(0).getStatus())) {
					pass = false;
				}
			}
			
			if (!pass) {
				// increase the error count
				increaseErrorCount(name + ',' + ip, today);
				// increase the wrong ip count
				increaseErrorCount(ip, today);

				throw new ApplicationException("无效的用户名或密码");
			}
			dbUser = pageInfo.getData(0);
		} else {
			dbUser = getDefaultAdminUser();
		}

		// it calls loginCallback.execute --> loginUser
		String userId = dbUser.getId().toString();
		safeManager.login(request, userId, site);

		checkEnableLoginCount();

//		String lastPath = safeManager.getLastVisitInfo(ServletActionContext.getRequest());
//		if (lastPath != null) {
//			// check permission
//			int index = lastPath.indexOf('?');
//			String checkPath = index > 0 ? lastPath.substring(0, index) : lastPath;
//			if (!safeManager.checkPage(userId, checkPath)) {
//				lastPath = null;
//			} else {
//				// remove site name
//				String sitePrefix = '/' + ContextHolder.get().getSiteName() + '/';
//				if (lastPath.startsWith(sitePrefix)) {
//					lastPath = lastPath.substring(sitePrefix.length() - 1);
//				}
//			}
//		}
//		lastPath = null;
//		if (lastPath == null || lastPath.equals("/")) {
//			lastPath = "/index.html";
//		}
		String lastPath = "/index.html";
		if (logger.isDebugEnabled()) {
			logger.debug("Last path is " + lastPath);
		}
		return lastPath;
	}
	
	private String getRemoteIp(HttpServletRequest request) {
		String ip = request.getHeader("X-Real-IP");
		if (ip == null || ip.length() == 0) {
			ip = request.getRemoteAddr();
		}
		return ip;
	}

	private void checkEnableLoginCount() {
		// 100,000 is enough for the license requirement
		if (maxEnabledLoginCount > 0 && loginCount < 100000) {
			if (loginCount == 0) {
				String value = systemDataService.getValue(SystemData.LOGIN_COUNT);
				if (value != null) {
					String realValue = SimpleEncryptUtil.decrypt(value);
					loginCount = Integer.parseInt(realValue);
				}
			}

			// don't think about the synchronized problem, it's not exact statistics
			loginCount++;

			String encryptValue = SimpleEncryptUtil.encrypt(String.valueOf(loginCount));
			systemDataService.setValue(SystemData.LOGIN_COUNT, encryptValue);

			if (loginCount > maxEnabledLoginCount) {
				ExceptionMappingInterceptor.interceptorEx = "序列号已经过期";
			}
		}
	}

	public LoginUser getDefaultAdminUser() {
		if (adminUser != null) {
			return adminUser;
		}

		adminUser = new LoginUser();
		adminUser.setName(LoginUser.SUPER_ADMIN);
		LoginUser theUser = genericService.searchAll(adminUser).getData(0);
		if (theUser != null) {
			adminUser = theUser;
		}
		Website website = new Website();
		website.setCompId(Website.DEFAULT_SID);
		website = genericService.searchAll(website).getData(0);
		return adminUser;
	}

	private void increaseErrorCount(String identity, Date today) {
		UserSecurity userSecurityExample = new UserSecurity();
		userSecurityExample.setIdentity(identity);
		UserSecurity userSecurity = genericService.searchAll(userSecurityExample).getData(0);
		if (userSecurity != null) {
			if (DateUtils.isSameDay(today, userSecurity.getLoginTime())) {
				userSecurity.setErrCount(userSecurity.getErrCount() + 1);
			} else {
				userSecurity.setErrCount(1);
				userSecurity.setLoginTime(today);
			}
			genericService.update(userSecurity);
		} else {
			userSecurity = userSecurityExample;
			userSecurity.setErrCount(1);
			userSecurity.setLoginTime(today);
			genericService.add(userSecurity);
		}
	}

	private Website getSite(String name) {
		return websiteService.getWebsite(name);
	}
	
	public Website getCurrentWebsite() {
		String site = getCurrentWebsiteName();
		return getSite(site);
	}

	private String getCurrentWebsiteName() {
		String site = ContextHolder.get().getSiteName();
		if (site == null) {
			site = websiteService.getDefaultWebsiteName();
		}
		return site;
	}

	// 自动权限1： privilege.mappingModelSimpleName(父)对应的权限
	// (注：privilege.modelSimpleName配置detail model权限)
	// 自动权限2： commonPermitPath
	// 权限范围： 页面, 页面字段, Model字段
	@Transactional
	public LoginUser loginUserBySite(String userId, String site,
			Executor<String, PathFieldPermission> fieldPermissionParser) {
		LoginUser dbUser = genericService.load(LoginUser.class, (userId));

		LoginUser user = new LoginUser();
		user.setId(dbUser.getId());
		user.setName(dbUser.getName());

		if (site == null) {
			site = ContextHolder.get().getSiteName();
		}

		/*
		 * 用户的权限之授权体系为：(注: 2&3为字段权限) 
		 * 1. 基于页面的授权：userTemplatePrivileges.privilege --> pagePermissions
		 * 2. 基于页面字段的授权:
		 * userTemplatePrivileges.privilege.fieldPermissions (字段授权页面) --> pageFieldPermissions
		 * 3. 基于Model的字段授权:
		 * userTemplatePrivileges.userTemplatePrivilegeFieldPermissions
		 * (目前与页面权限中的其它权限放在了一起) --> modelFieldPrivileges
		 * 4. 基于search页面的记录过滤控制:
		 * --> pagePermissions.scopeTpe
		 */
		if (safeManager.isEnablePermissionCheck() && !LoginUser.SUPER_ADMIN.equals(user.getName())) {
			if (fieldPermissionParser == null) {
				fieldPermissionParser = this.fieldPermissionParser;
			}
			
			// authorizate permission to user
			List<PageFieldPermission> userPageFieldPermission = dbUser.getPageFieldPermissions();
			List<ModelFieldPermission> modelFieldPrivileges = dbUser.getModelFieldPermissions();

			// 1. page permission ( user -- template -- page privilege )
			List<Privilege> recordPrivileges = new ArrayList<Privilege>();
			Set<String> enabledSites = new HashSet<String>();
			// TODO: for better performance, only load the page permission in
			// current website?
			for (Privilege privilege : dbUser.getPagePermissions()) {
				addPagePermission(userId, privilege);

				// Statistics the sites which the user can access
				enabledSites.add(privilege.getProductName());

				// record limitation
				if (!ScopeType.Global.equals(privilege.getScopeType())) { // it's field permission
					recordPrivileges.add(privilege);
				}
			}

			// 2. field permission - read / write, page level autoPageConverterManager will call
			// safeManager.checkReadField/safeManager.checkWriteField to check the field permission
			GroupPrivilege groupPrivilege = privilegeService.getGroupPrivilege((userId));
			if (userPageFieldPermission != null) {
				for (PageFieldPermission pageFieldPermission : userPageFieldPermission) {
					Privilege privilege = pageFieldPermission.getUserTemplatePrivilege().getPrivilege();
					String mappingName = privilege.getMappingModelSimpleName();
					if (mappingName == null) {
						mappingName = privilege.getModelSimpleName();
					}
					Collection<String> mappingMethods = groupPrivilege.getMethodMapping(mappingName, privilege.getMethod());
	
					// add the field limitation to all the accessible path
					String modelName = beanIntrospector.getModelName(privilege.getModelSimpleName());
					if (mappingMethods != null && mappingMethods.size() > 0) {
						for (String method : mappingMethods) {
							String path = functions.linkModelNoBase(modelName, method, true);
							if (safeManager.checkPage(userId, path)) {
								addFieldLimition(userId, path, pageFieldPermission, fieldPermissionParser);
							}
						}
					} else {
						String path = functions.linkModelNoBase(modelName, privilege.getMethod(), true);
						if (safeManager.checkPage(userId, path)) {
							addFieldLimition(userId, path, pageFieldPermission, fieldPermissionParser);
						}
					}
				}
			}

			// 3. field permission - read / write, model level
			if (modelFieldPrivileges != null) {
				for (ModelFieldPermission modelFieldPermission : modelFieldPrivileges) {
					String modelName = beanIntrospector.getModelName(modelFieldPermission.getModelSimpleName());
					addFieldLimition(userId, modelName, modelFieldPermission, fieldPermissionParser);
	
					// get the reference field name
					List<String> refPropertyNames = beanIntrospector.getModelInfo(modelName).getRefByProperties(
							modelFieldPermission.getFieldName());
					if (refPropertyNames != null) {
						FieldPermission refModelFieldPermission = modelFieldPermission.copyTo(null);
						for (String refPropertyName : refPropertyNames) {
							refModelFieldPermission.setFieldName(refPropertyName);
							addFieldLimition(userId, modelName, refModelFieldPermission, fieldPermissionParser);
						}
					}
	
					// TODO: the mapping model name?
				}
			}

			// every one can visit the home page
			for (String theSite : enabledSites) {
				Website theWebsite = getSite(site);
				if (theWebsite != null) {
					// just for passing the permission, the page redirects in StrutsExecuteFilter.redirectToFirstProduct
					String sitePomePage = '/' + "espirit" + functions.linkNoBase("/index", false);
					safeManager.addPagePermission(userId, sitePomePage);

					String productName = "espirit";
//					if (theWebsite.getDefaultTemplate() != null) {
//						productName = theWebsite.getDefaultTemplate().getName();
//					} else {
//						productName = theSite;
//					}
					safeManager.addPagePermission(userId, functions.linkNoBase("/" + productName + "/index", true));
				}
			}

			for (String path : commonPermitPath) {
				safeManager.addPagePermission(userId, functions.linkNoBase(path, true));
			}
		} else {
			if (config.isEnableSitePath()) {
				safeManager.addPagePermission(userId, "/" + site + "/");
			} else {
				safeManager.addPagePermission(userId, "/");
			}
		}

		SafeManager.getInstance().setUserInfo(userId, user);
		return user;
	}

	private void addFieldLimition(String userId, String path, FieldPermission fieldPermission,
			Executor<String, PathFieldPermission> fieldPermissionParser) {
		if (FieldPermissionType.NONE.equals(fieldPermission.getFieldPermissionType())) { // no permission
			addReadLimition(userId, path, fieldPermission.getFieldName(), null);
			addWriteLimition(userId, path, fieldPermission.getFieldName(), null);
		} else if (FieldPermissionType.READ.equals(fieldPermission.getFieldPermissionType())) { // can only read
			addWriteLimition(userId, path, fieldPermission.getFieldName(), null);
			String condition = fieldPermissionParser.execute(new PathFieldPermission(path, fieldPermission));
			if (condition != null) {
				// the read permission with limitation
				addReadLimition(userId, path, fieldPermission.getFieldName(), condition);
			}
		} else if (FieldPermissionType.READWRITE.equals(fieldPermission.getFieldPermissionType())) {
			// write with condition
			String writeCondition = fieldPermissionParser.execute(new PathFieldPermission(path, fieldPermission));
			if (writeCondition != null) {
				addWriteLimition(userId, path, fieldPermission.getFieldName(), writeCondition);
			}
			// read with condition
			String condition = fieldPermissionParser.execute(new PathFieldPermission(path, fieldPermission));
			if (condition != null) {
				addReadLimition(userId, path, fieldPermission.getFieldName(), condition);
			}
		}
	}

	private void addPagePermission(String userId, Privilege privilege) {
		for (String path : privilegeService.toPathsWithSite(privilege)) {
			safeManager.addPagePermission(userId, path);
		}
	}

	@Protected
	public String getEncryptPassword(String password) {
		if (password == null) {
			return null;
		}
		try {
			byte[] encryptPassword = MessageDigest.getInstance("MD5").digest(password.getBytes());
			String base64Password = Base64.encodeBase64String(encryptPassword);
			String SEED = "0933324145462219732314329";
			int RADIX = 16;
			BigInteger bi_text = new BigInteger(base64Password.getBytes("UTF-8"));
			BigInteger bi_r0 = new BigInteger(SEED);
			BigInteger bi_r1 = bi_r0.xor(bi_text);
			return bi_r1.toString(RADIX);
		} catch (Exception e) {
			logger.error("Failed to encrypt password", e);
			return password;
		}
	}

	private void addReadLimition(String userId, String path, String fieldName, String condition) {
		PermissionRule rule = new FreemarkerSQLRule();
		rule.setName(fieldName);
		rule.setCondition(condition);
		safeManager.addFieldLimit(userId, path, fieldName, rule, true);
	}

	private void addWriteLimition(String userId, String path, String fieldName, String condition) {
		PermissionRule rule = new FreemarkerSQLRule();
		rule.setName(fieldName);
		rule.setCondition(condition);
		safeManager.addFieldLimit(userId, path, fieldName, rule, false);
	}

	@Rest(simpleServiceName = "loginUser", type = "free")
	public Result logout() {
		safeManager.logout(ServletActionContext.getRequest());
		String siteName = ContextHolder.get().getSiteName();
		Result result = new ServletRedirectResult("/" + siteName + "/");
		objectFactory.autoWireBean(result);
		return result;
	}

	public PageInfo search(ParameterMap model, int start, int limit, String sort, String dir, boolean newSearch,
			Reference<QLInfo> qlInfoRef, @ParamScope(visible = false, value = "#ps") PageElements ps) {
		initElements(ps);
		return genericService.search(model, start, limit, sort, dir, newSearch, qlInfoRef);
	}

	private void initElements(PageElements ps) {
		ps.setElementVisible("execute.callFlag", false);
		ps.setElementLength("execute.callFlag", 30);
		ps.setElementVisible("execute.callFlagAllocate", false);
		// ps.setElementLength( "execute.name", 150);
		// ps.setElementLength( "execute.code", 100);
		// ps.setElementLength( "execute.email", 80);
		// ps.setElementLength( "execute.tel", 80);
		// ps.setElementLength( "execute.description", 300);
	}

	protected void setElementVisible(PageElements ps, String elementId, boolean visible) {
		PageElement pe = ps.getElement(elementId);
		if (pe != null) {
			pe.setHidden(!visible);
		}
	}

	@Rest(simpleServiceName = "anonymity", type = "free")
	public void forgetPassword(String name) {
		if (name == null || name.length() == 0) {
			logger.warn("Can't find user:" + name);
			return;
		}

		LoginUser user = new LoginUser();
		user.setName(name);
		List<LoginUser> users = genericService.searchAll(user).getData();
		if (users.isEmpty()) {
			logger.warn("Can't find user:" + name);
			return;
		} else {
			user = users.get(0);
		}

		if (user.getEmail() == null) {
			throw new ApplicationException("用户邮箱未配置,请联系管理员重置密码");
		}

		UserPasswordReset userPasswordReset = genericService.load(UserPasswordReset.class, user.getId());
		if (userPasswordReset == null) {
			userPasswordReset = new UserPasswordReset();
			userPasswordReset.setId(user.getId());
		}
		// encrypt the password again and use the second encrypted password and time as the key
		String key = getEncryptPassword(user.getPassword() + System.currentTimeMillis());
		StringBuilder sKey = new StringBuilder(key.length());
		for (char c : key.toCharArray()) {
			if (Character.isLetterOrDigit(c)) {
				sKey.append(c);
			} else {
				sKey.append('*');
			}
		}
		userPasswordReset.setVerifyKey(sKey.toString());
		userPasswordReset.setUpdateTime(Calendar.getInstance().getTime());
		genericService.merge(userPasswordReset);

		String resetLink = "http://" + config.getHost() + Functions.getBase()
				+ "/eap/sa/user.resetPassword.html?model.id=" + userPasswordReset.getId() + "&model.verifyKey="
				+ userPasswordReset.getVerifyKey();
		Map<String, String> data = new HashMap<String, String>(2);
		data.put("resetLink", resetLink);
		data.put("username", user.getName());
		mailService.send("重置密码 ", "/pages/emailTpl/forget-password-email.ftl", new String[] { user.getEmail() }, data);
	}

	private boolean enableResetPassword(UserPasswordReset userPasswordReset) {
		UserPasswordReset passwordReset = genericService.load(UserPasswordReset.class, userPasswordReset.getId());
		if (passwordReset != null) {
			if (passwordReset.getVerifyKey().equals(userPasswordReset.getVerifyKey())) {
				int hours = (int) (System.currentTimeMillis() - passwordReset.getUpdateTime().getTime()) / 1000 / 3600;
				if (hours < 24) {
					return true;
				}
			}
		}
		return false;
	}

	@Rest(simpleServiceName = "anonymity", type = "free")
	public void warning() {
	}

	@Rest(simpleServiceName = "anonymity", type = "free")
	public Result resetPassword(UserPasswordReset model, String password) {
		if (enableResetPassword(model)) {
			// login to the default site
			safeManager.login(ServletActionContext.getRequest(), model.getId().toString(), null);

			String pageId = "_changePassword";
			String siteName = getCurrentWebsite().getName();
			return new ServletRedirectResult("/" + siteName + "/index.html?subpage=" + pageId);
		} else {
			return new ServletRedirectResult("/");
		}
	}

	@Rest(simpleServiceName = "loginUser", type = "free")
	public String _changePassword() {
		LoginUser currentUser = getLoginUser();
		UserPasswordReset passwordReset = genericService.load(UserPasswordReset.class, currentUser.getId());
		String verifyKey = null;
		if (passwordReset != null) {
			int hours = (int) (System.currentTimeMillis() - passwordReset.getUpdateTime().getTime()) / 1000 / 3600;
			if (hours < 24) {
				verifyKey = passwordReset.getVerifyKey();
			}
		}
		return verifyKey;
	}

	@Rest(simpleServiceName = "loginUser", type = "free")
	public void changePassword(String verifyKey, String oldPassword, String password) {
		LoginUser currentUser = getLoginUser();

		LoginUser dbUser = genericService.load(LoginUser.class, currentUser.getId());
		if (verifyKey != null) {
			UserPasswordReset userPasswordReset = new UserPasswordReset();
			userPasswordReset.setId(currentUser.getId());
			userPasswordReset.setVerifyKey(verifyKey);

			if (!enableResetPassword(userPasswordReset)) {
				throw new ApplicationException("重置密码的密钥过期!");
			}
		} else {
			String oldEncryptPassword = getEncryptPassword(oldPassword);
			boolean pass = oldEncryptPassword == null && dbUser.getPassword() == null
					|| oldEncryptPassword != null && oldEncryptPassword.equals(dbUser.getPassword());
			if (!pass) {
				throw new ApplicationException("旧密码不正确!");
			}
		}

		String encryptPassword = getEncryptPassword(password);
		dbUser.setPassword(encryptPassword);
		genericService.update(dbUser);
	}

	Map<String, String> loginUserCache;

	public String getLoginUserIdByCode(String code) {
		if (code == null)
			return null;
		if (loginUserCache.get(code) == null) {
			LoginUser search = new LoginUser();
			search.setName(code);
			LoginUser theUser = genericService.searchAll(search).getData(0);
			if (theUser == null) {
				return null;
			}
			loginUserCache.put(code, theUser.getId());
		}
		return loginUserCache.get(code);
	}
}
